package com.gmrz.uaf.policy.verification;
 
 import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.*;
 
 public class RegistrationPolicyVerifier
   implements IPolicyVerifier<RegistrationAssertion>
 {
   private static final Logger LOG = LogManager.getLogger(RegistrationPolicyVerifier.class);
 
   public void filterDisallowedAssertions(List<RegistrationAssertion> assertions, Map<String, AuthenticatorSpec> m, Policy policy)
     throws PolicyVerificationException
   {
     try
     {
       LOG.debug(" Filter disallowed criterias");
 
       PolicyVerificationUtil.verifyInputs(assertions, m, policy);
       Iterator i$;
       MatchCriteria dc;
       Iterator registrationAssertionIter;

       if ((policy.getDisallowed() != null) && (policy.getDisallowed().size()!=0))
       {
         List disallowedCriteria = policy.getDisallowed();
         for (i$ = disallowedCriteria.iterator(); i$.hasNext(); ) { 
        	 dc = (MatchCriteria)i$.next();
           for (registrationAssertionIter = assertions.iterator(); registrationAssertionIter.hasNext(); )
           {
             RegistrationAssertion assertion = (RegistrationAssertion)registrationAssertionIter.next();
 
             String aaid = assertion.getAuthenticatorResponse().getKRD().getAAID();
             AuthenticatorSpec spec = PolicyVerificationUtil.getAuthenticatorSpecForAAID(aaid, m);
 
             if ((dc.getKeyIDList() != null) && (dc.getAAID() != null))
             {
               boolean result = checkAlreadyExistingRegistration(dc, assertion);
 
               if (result == true)
               {
                 LOG.info(" This registration already exists; AAID: {}", aaid);
                 registrationAssertionIter.remove();
               }
 
             }
             else if (Matcher.compare(dc, spec))
             {
               LOG.debug(" Assertion matches the disallowed MatchCriteria, removing the assertion from the list with AAID : {} ", spec.getAAID().format());
 
               registrationAssertionIter.remove();
             }
           } }
       }
       else {
         LOG.debug("There is no disallowed match criteria in the policy : {}", policy.getPolicyName());
       }
     }
     catch (PolicyVerificationException pve) {
       LOG.error("Error occurred during processing disallowed matchCriteria for Registration policy Verification : {}", pve.getMessage());
       throw pve;
     }
     catch (Exception e) {
       LOG.error("Following Error occurred during Registration policy Verification : {}", e.getMessage());
       throw new PolicyVerificationException(UAFErrorCode.SYS_POLICY_VERIFICATION_FAILED, "Error occurred during Registration policy Verification");
     }
   }
 
   public List<RegistrationAssertion> selectAcceptedAssertions(List<RegistrationAssertion> assertions, Map<String, AuthenticatorSpec> m, Policy policy)
     throws PolicyVerificationException
   {
     try
     {
       PolicyVerificationUtil.verifyInputs(assertions, m, policy);
 
       List acceptedMatchCriteria = policy.getAccepted();
 
       boolean matchSuccessful = false;
 
       List<RegistrationAssertion> intermidiateSelectedAssertions = new ArrayList();
 
       List finalSelectedAssertions = new ArrayList();
 
       for (Iterator iterator = acceptedMatchCriteria.iterator(); iterator.hasNext(); )
       {
         List<MatchCriteria> acceptedMatchCriteriaIndex = (List)iterator.next();
         LOG.debug("Iterate the accepted set(s) of match criterias.");
 
         for (MatchCriteria matchCriteria : acceptedMatchCriteriaIndex) {
           matchSuccessful = false;
           for (RegistrationAssertion assertion : assertions)
           {
             String aaid = assertion.getAuthenticatorResponse().getKRD().getAAID();
             AuthenticatorSpec spec = PolicyVerificationUtil.getAuthenticatorSpecForAAID(aaid, m);
 
             if (isMatch(assertion, spec, matchCriteria)) {
               matchSuccessful = true;
               if (!intermidiateSelectedAssertions.contains(assertion)) {
                 intermidiateSelectedAssertions.add(assertion);
               }
             }
           }
           if (!matchSuccessful)
           {
             intermidiateSelectedAssertions.clear();
 
             break;
           }
         }
 
         if (intermidiateSelectedAssertions.size() > 0) {
           for (RegistrationAssertion assertion : intermidiateSelectedAssertions) {
             if (!finalSelectedAssertions.contains(assertion)) {
               finalSelectedAssertions.add(assertion);
             }
           }
 
           intermidiateSelectedAssertions.clear();
         }
       }
 
       if (finalSelectedAssertions.size() == 0) {
         LOG.error("Policy matching failed. there are no valid assertions matching the policy");
         throw new PolicyVerificationException(UAFErrorCode.SYS_POLICY_VERIFICATION_FAILED,
                 "Policy matching failed. there are no valid assertions matching the policy");
       }
 
       LOG.debug("Completed policy verification for registration operation.");
       return finalSelectedAssertions;
     }
     catch (PolicyVerificationException pve) {
       LOG.error("Error occurred during processing accepted matchCriteria for Registration policy Verification : {} ", pve.getMessage());
       throw pve;
     }
     catch (Exception e) {
       LOG.error("Following Error occurred during Registration policy Verification : {} ", e.getMessage());
     }throw new PolicyVerificationException(UAFErrorCode.SYS_POLICY_VERIFICATION_FAILED, "Error occurred during Registration policy Verification");
   }
 
   private boolean validateAssertionTagsForRegistration(RegistrationAssertion assertion, AuthenticatorSpec spec, MatchCriteria matchCriteria)
     throws PolicyVerificationException
   {
     boolean isMatch = false;
 
     String assertionAAID = assertion.getAuthenticatorResponse().getKRD().getAAID();
     int authenticatorVersionInAssertion = assertion.getAuthenticatorResponse().getKRD().getAuthenticatorVersion();
 
     boolean checkVersion = PolicyVerificationUtil.checkAuthenticatorVersion(matchCriteria, assertionAAID, authenticatorVersionInAssertion);
     if (!checkVersion) {
       LOG.debug("Validation of the assertion against the authenticator spec failed for authentication version(in assertion) : {} ", Integer.valueOf(authenticatorVersionInAssertion));
       return false;
     }
 
     if (spec.getAlgorithm() != assertion.getAuthenticatorResponse().getKRD().getSignAlgorithm()) {
       LOG.debug("Validation of the assertion against the authenticator spec failed for authentication algo(in assertion) : {} ", Integer.valueOf(assertion.getAuthenticatorResponse().getKRD().getSignAlgorithm()));
       return false;
     }
 
     if (spec.getKeyFormat() != assertion.getAuthenticatorResponse().getKRD().getKeyFormat()) {
       LOG.debug("Validation of the assertion against the authenticator spec failed for keyFormat(in assertion) : {} ", Integer.valueOf(assertion.getAuthenticatorResponse().getKRD().getKeyFormat()));
       return false;
     }
 
     if (!spec.getAssertionScheme().equalsIgnoreCase(assertion.getKeyRegistrationDataScheme())) {
       LOG.debug("Validation of the assertion against the authenticator spec failed for supported scheme(in assertion) : {} ", assertion.getKeyRegistrationDataScheme());
       return false;
     }
 
     int assertionAttestationType = assertion.getAuthenticatorResponse().getAttestationType().getTag();
 
     isMatch = PolicyVerificationUtil.containsIntegerInList(spec.getAttestationTypes(), assertionAttestationType);
 
     if (!isMatch) {
       LOG.debug("Validation of the assertion against the authenticator spec failed, authenticatorSpec attestationType does not match Assertion AttestationType : {}", Integer.valueOf(assertionAttestationType));
       return false;
     }
 
     if (matchCriteria.getPreferredAttestationTypes() != null)
     {
       isMatch = PolicyVerificationUtil.containsIntegerInList(matchCriteria.getPreferredAttestationTypes(), assertionAttestationType);
 
       if (!isMatch) {
         LOG.debug("Validation of the assertion against the authenticator spec failed, policy MatchCriteria attestationType does not match Assertion AttestationType : {} ", Integer.valueOf(assertionAttestationType));
         return false;
       }
 
     }
 
     LOG.debug("All the required tags in the registrationAssertion are validated correctly");
     return true;
   }
 
   private boolean checkAlreadyExistingRegistration(MatchCriteria matchCriteria, RegistrationAssertion assertion) throws PolicyVerificationException {
     if ((assertion == null) || (matchCriteria == null))
       throw new PolicyVerificationException(UAFErrorCode.SYS_POLICY_VERIFICATION_FAILED, "Cannot process null for verifying existing registration");
     KeyID assertionKeyID;
     AAID assertionAAID;
     if ((matchCriteria.getKeyIDList() != null) && (matchCriteria.getAAID() != null))
     {
       assertionKeyID = new KeyID();
       assertionKeyID = assertionKeyID.withId(assertion.getAuthenticatorResponse().getKRD().getKeyID());
 
       assertionAAID = new AAID();
       assertionAAID = assertionAAID.parse(assertion.getAuthenticatorResponse().getKRD().getAAID());
 
       LOG.debug(" debug: remove this : the keyID in the assertion is : {}", assertionKeyID.toString());
       KeyID keyID;
       for (Iterator i$ = matchCriteria.getKeyIDList().iterator(); i$.hasNext(); ) { 
    	   keyID = (KeyID)i$.next();
         for (AAID aaid : matchCriteria.getAAID())
           if ((Arrays.equals(keyID.getId(), assertionKeyID.getId())) && (aaid.equals(assertionAAID))) {
             LOG.debug(" there is keyID,aaid match, so this is an existing registration; ");
             return true;
           }
       }
     }
     return false;
   }
 
   private boolean isMatch(RegistrationAssertion assertion, AuthenticatorSpec spec, MatchCriteria matchCriteria) throws PolicyVerificationException {
     if (Matcher.compare(matchCriteria, spec))
     {
       boolean isValidAssertion = validateAssertionTagsForRegistration(assertion, spec, matchCriteria);
       if (isValidAssertion == true) {
         return true;
       }
     }
 
     return false;
   }
 }
